// Tencent is pleased to support the open source community by making ncnn available.
//
// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

#include <android/asset_manager_jni.h>
#include <android/native_window_jni.h>
#include <android/native_window.h>

#include <android/log.h>
#include <android/bitmap.h>
#include <jni.h>

#include <string>
#include <vector>

#include <platform.h>
#include <benchmark.h>

#include "yolo.h"

#include "ndkcamera.h"

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include<iostream>
#if __ARM_NEON
#include <arm_neon.h>
#endif // __ARM_NEON

static int draw_unsupported(cv::Mat& rgb)
{
    const char text[] = "unsupported";

    int baseLine = 0;
    cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 1.0, 1, &baseLine);

    int y = (rgb.rows - label_size.height) / 2;
    int x = (rgb.cols - label_size.width) / 2;

    cv::rectangle(rgb, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),
                    cv::Scalar(255, 255, 255), -1);

    cv::putText(rgb, text, cv::Point(x, y + label_size.height),
                cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 0));

    return 0;
}

static int draw_fps(cv::Mat& rgb)
{
    // resolve moving average
    float avg_fps = 0.f;
    {
        static double t0 = 0.f;
        static float fps_history[10] = {0.f};

        double t1 = ncnn::get_current_time();
        if (t0 == 0.f)
        {
            t0 = t1;
            return 0;
        }

        float fps = 1000.f / (t1 - t0);
        t0 = t1;

        for (int i = 9; i >= 1; i--)
        {
            fps_history[i] = fps_history[i - 1];
        }
        fps_history[0] = fps;

        if (fps_history[9] == 0.f)
        {
            return 0;
        }

        for (int i = 0; i < 10; i++)
        {
            avg_fps += fps_history[i];
        }
        avg_fps /= 10.f;
    }

    char text[32];
    sprintf(text, "FPS=%.2f", avg_fps);

    int baseLine = 0;
    cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);

    int y = 0;
    int x = rgb.cols - label_size.width;

    cv::rectangle(rgb, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),
                    cv::Scalar(255, 255, 255), -1);

    cv::putText(rgb, text, cv::Point(x, y + label_size.height),
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));

    return 0;
}

static Yolo* g_yolo = 0;
static Yolo* unit_yolo=0;
static ncnn::Mutex lock;

class MyNdkCamera : public NdkCameraWindow
{
public:
    virtual void on_image_render(cv::Mat& rgb) const;
};

void MyNdkCamera::on_image_render(cv::Mat& rgb) const
{
    // nanodet
    {
        ncnn::MutexLockGuard g(lock);
        double s=0;
        if (g_yolo)
        {
            std::vector<Object> objects;
            g_yolo->detect(rgb, objects);

            g_yolo->draw(rgb, objects,s);
        }
        else
        {
            draw_unsupported(rgb);
        }
    }

    draw_fps(rgb);
}

static MyNdkCamera* g_camera = 0;

extern "C" {

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "JNI_OnLoad");

    g_camera = new MyNdkCamera;

    return JNI_VERSION_1_4;
}

JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
{
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "JNI_OnUnload");

    {
        ncnn::MutexLockGuard g(lock);

        delete g_yolo;
        g_yolo = 0;
        delete unit_yolo;
        unit_yolo=0;
    }

    delete g_camera;
    g_camera = 0;
}

// public native boolean loadModel(AssetManager mgr, int modelid, int cpugpu);
JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_loadModel(JNIEnv* env, jobject thiz, jobject assetManager, jint modelid, jint cpugpu)
{
    if (modelid < 0 || modelid > 6 || cpugpu < 0 || cpugpu > 1)
    {
        return JNI_FALSE;
    }
    //NDK是AAssetManager,获取assets中的所有文件

    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);

    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "loadModel %p", mgr);

    const char* modeltypes[] =
    {

        "uv",
        "unit"
    };

    const int target_sizes[] =
    {
        1024,
        1024,
    };

    const float mean_vals[][3] =
    {
        {103.53f, 116.28f, 123.675f},
        {103.53f, 116.28f, 123.675f},
    };

    const float norm_vals[][3] =
    {
        { 1 / 255.f, 1 / 255.f, 1 / 255.f },
        { 1 / 255.f, 1 / 255.f, 1 / 255.f },
    };

    const char* modeltype = modeltypes[(int)modelid];
    int target_size = target_sizes[(int)modelid];
    bool use_gpu = (int)cpugpu == 1;

    // reload
    {
        ncnn::MutexLockGuard g(lock);

        if (use_gpu && ncnn::get_gpu_count() == 0)
        {
            // no gpu
            delete g_yolo;
            g_yolo = 0;
            delete unit_yolo;
            unit_yolo=0;
            __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "load no gpu");
        }
        else
        {   if(modelid==0){
                if (!g_yolo)
                    g_yolo = new Yolo;
                g_yolo->load(mgr, modeltype, target_size, mean_vals[(int)modelid], norm_vals[(int)modelid], use_gpu);
                __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "has gpu");
        }
        else{
                if (!unit_yolo)
                    unit_yolo = new Yolo;
                unit_yolo->load(mgr, modeltype, target_size, mean_vals[(int)modelid], norm_vals[(int)modelid], use_gpu);
                __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "has gpu");
        }

        }
    }

    return JNI_TRUE;
}
//
JNIEXPORT void JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_usbInfer(JNIEnv *env,jclass obj,jobject bmpIn,jobject bmpOut){
    __android_log_print(ANDROID_LOG_DEBUG,"ncnn","usbInfer:start");
    AndroidBitmapInfo inBmpInfo;
    void* inPixelsAddress;
    int ret;
    if((ret= AndroidBitmap_getInfo(env,bmpIn,&inBmpInfo))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_getInfo() failed! error=%d", ret);
        return;
    }
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "original image::width is %d,height is %d,format is %d,flags is %d,stride is %u", inBmpInfo.width,inBmpInfo.height,inBmpInfo.format,inBmpInfo.flags,inBmpInfo.stride);
    if((ret= AndroidBitmap_lockPixels(env,bmpIn,&inPixelsAddress))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_lockPixels() failed! error=%d", ret);
    }
    cv::Mat inMat(inBmpInfo.height,inBmpInfo.width,CV_8UC4,inPixelsAddress);
    cv::cvtColor(inMat, inMat, cv::COLOR_RGBA2BGR);
    double s=0;
        if (g_yolo) {
            __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "detect");
            std::vector<Object> objects;

            g_yolo->detect(inMat, objects);
            __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "detect=%d", objects.size());
            if(objects.size()>0){
                __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "label=%d", objects[0].label);
            }
            g_yolo->draw(inMat, objects,s);
        } else {
            draw_unsupported(inMat);
        }

    AndroidBitmapInfo outBmpInfo;
    void* outPixelsAddress;

    if ((ret = AndroidBitmap_getInfo(env, bmpOut, &outBmpInfo)) < 0) {
//        LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }

    if ((ret = AndroidBitmap_lockPixels(env, bmpOut, &outPixelsAddress)) < 0) {
//        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return;
    }

    cv::Mat outMat(outBmpInfo.height, outBmpInfo.width, CV_8UC4, outPixelsAddress);
    cv::cvtColor(inMat, outMat, cv::COLOR_BGR2RGBA);
    //相当于直接操作了内存地址
    AndroidBitmap_unlockPixels(env,bmpIn);
    AndroidBitmap_unlockPixels(env, bmpOut);
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "return!!!");
    return;
}
// public native boolean openCamera(int facing);
JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_openCamera(JNIEnv* env, jobject thiz, jint facing)
{
    if (facing < 0 || facing > 1)
        return JNI_FALSE;

    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "openCamera %d", facing);

    g_camera->open((int)facing);

    return JNI_TRUE;
}

// public native boolean closeCamera();
JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_closeCamera(JNIEnv* env, jobject thiz)
{
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "closeCamera");

    g_camera->close();

    return JNI_TRUE;
}

// public native boolean setOutputWindow(Surface surface);
JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_setOutputWindow(JNIEnv* env, jobject thiz, jobject surface)
{
    ANativeWindow* win = ANativeWindow_fromSurface(env, surface);

    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "setOutputWindow %p", win);

    g_camera->set_window(win);

    return JNI_TRUE;
}
JNIEXPORT jobject JNICALL
Java_com_tencent_yolov8ncnn_Yolov8Ncnn_oilanddruffDetect(JNIEnv *env, jobject thiz, jobject bmpIn,
                                                 jobject bmpOut,jint flag) {
    // TODO: implement usbInfer1()
    oil_return oil;
    // 获取 OilReturn 类
    jclass oilReturnClass = env->FindClass("com/tencent/yolov8ncnn/OilReturn");
    // 创建 OilReturn 对象
    jobject javaOilReturn = env->AllocObject(oilReturnClass);
    __android_log_print(ANDROID_LOG_DEBUG,"ncnn","usbInfer:start");
    AndroidBitmapInfo inBmpInfo;
    void* inPixelsAddress;
    int ret;
    if((ret= AndroidBitmap_getInfo(env,bmpIn,&inBmpInfo))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_getInfo() failed! error=%d", ret);
        return javaOilReturn;
    }
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "original image::width is %d,height is %d,format is %d,flags is %d,stride is %u", inBmpInfo.width,inBmpInfo.height,inBmpInfo.format,inBmpInfo.flags,inBmpInfo.stride);
    if((ret= AndroidBitmap_lockPixels(env,bmpIn,&inPixelsAddress))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_lockPixels() failed! error=%d", ret);
    }
    cv::Mat inMat(inBmpInfo.height,inBmpInfo.width,CV_8UC4,inPixelsAddress);
    cv::cvtColor(inMat, inMat, cv::COLOR_RGBA2BGR);
    std::vector<Object> final;
    double s=0;
    if (g_yolo) {
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "detect");
        std::vector<Object> objects;
        g_yolo->detect(inMat, objects);
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "detect=%d", objects.size());
        if(objects.size()>0){
            __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "label=%d", objects[0].label);
        }
        //如果采用的是油脂检测
        if(flag==0){
           for(int i=0;i<objects.size();i++){
               if(objects[i].label==0){
                   final.push_back(objects[i]);
               }
           }
        }else{
            //如果是角质检测
            for(int i=0;i<objects.size();i++){
                if(objects[i].label==1){
                    final.push_back(objects[i]);
                }
            }
        }
        g_yolo->draw(inMat, final,s);
    } else {
        draw_unsupported(inMat);
    }

    AndroidBitmapInfo outBmpInfo;
    void* outPixelsAddress;

    if ((ret = AndroidBitmap_getInfo(env, bmpOut, &outBmpInfo)) < 0) {
//        LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return javaOilReturn;
    }

    if ((ret = AndroidBitmap_lockPixels(env, bmpOut, &outPixelsAddress)) < 0) {
//        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return javaOilReturn;
    }
    cv::Mat outMat(outBmpInfo.height, outBmpInfo.width, CV_8UC4, outPixelsAddress);
    cv::cvtColor(inMat, outMat, cv::COLOR_BGR2RGBA);
    //相当于直接操作了内存地址
    AndroidBitmap_unlockPixels(env,bmpIn);
    AndroidBitmap_unlockPixels(env, bmpOut);
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "return!!!");
    // 获取字段 ID
    jfieldID suggestionField = env->GetFieldID(oilReturnClass, "suggestion", "Ljava/lang/String;");
    jfieldID levelField = env->GetFieldID(oilReturnClass, "level", "D");
    jfieldID statusCodeField = env->GetFieldID(oilReturnClass, "status_code", "I");
    jfieldID describeField = env->GetFieldID(oilReturnClass, "describe", "Ljava/lang/String;");
    jfieldID severityField = env->GetFieldID(oilReturnClass, "severity", "Ljava/lang/String;");
    //处理油脂逻辑
    if(flag==0){
        if (s < 0.25) {
            double base = static_cast<double>(1) / 25;
            oil.level = 6 - base * s * 100;
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "水油不均，油脂形态初步形成";
            oil.suggestion = "基础清洁";
            oil.serverity = "适中";
        }
        else if (s >= 0.25 && s < 0.5) {
            double base = static_cast <double>(2) / 25;
            oil.level = 5 - base * (s * 100 - 25);
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "油脂呈半流动液态，附着在毛孔周边，油脂分泌轻度旺盛，影响表皮养分吸收以及摄入营养";
            oil.suggestion = "清洁油脂，调整头皮酸碱度";
            oil.serverity = "轻度";
        }
        else if (0.5 <= s && s < 0.75) {
            double base = static_cast <double>(2) / 25;
            oil.level = 3 - base * (s * 100 - 50);
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "油脂呈液态，发干根部明显油腻，油脂分泌旺盛，形成半液态状对毛囊口呈围堵状态，养分摄入不均，拉长毛发代谢周期，影响毛发健康生长";
            oil.suggestion = "清洁纠正，调节DHT，供给营养";
            oil.serverity = "中度";
        }
        else {
            double base = static_cast <double>(1) / 25;
            oil.level = 1 - base * (s * 100 - 75);
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "油脂呈凝固状，蜡状物体包裹在毛囊口以及发干周边，蜡状物是分泌过多的油脂后，油脂吸附空气当中的灰尘等和头屑角质混合而成的类似蜡状物的东西，不易清除，长期附着堵塞毛囊口，循环代谢差，血氧供给不足，导致毛发逐步退化。";
            oil.suggestion = "清洁纠正，配合仪器以及敏感修复配方的同时，分解去除蜡状角质，补给营养";
            oil.serverity = "重度";
        }
    }else{
        if (s < 0.25) {
            double base = static_cast<double>(1) / 25;
            oil.level = 6 - base * s * 100;
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "头皮表层细胞更新，死皮细胞初步堆积";
            oil.suggestion = "基础清洁";
            oil.serverity = "适中";
        }
        else if (s >= 0.25 && s < 0.5) {
            double base = static_cast <double>(2) / 25;
            oil.level = 5 - base * (s * 100 - 25);
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "头皮表层有轻微的角质堆积，外观比较细腻，随时间推移可能会角质化，影响毛发生长";
            oil.suggestion = "规律清洁，按摩头皮刺激血液循环，帮助头皮更新角质";
            oil.serverity = "轻度";
        }
        else if (0.5 <= s && s < 0.75) {
            double base = static_cast <double>(2) / 25;
            oil.level = 3 - base * (s * 100 - 50);
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "角质层较厚，头皮表面有较为明显的鳞片状死皮。头皮的自我更新受到干扰，导致死皮细胞堆积，影响毛囊健康，干扰毛发正常生长";
            oil.suggestion = "规律清洁，注重保湿和修复，防止头皮过度干燥或油腻";
            oil.serverity = "中度";
        }
        else {
            double base = static_cast <double>(1) / 25;
            oil.level = 1 - base * (s * 100 - 75);
            oil.level = round(oil.level * 100) / 100;
            oil.describe = "头皮表层有重度的角质堆积，鳞片状死皮增多，影响毛囊健康，干扰正常的毛发生长";
            oil.suggestion = "注重头皮的保湿和修复，增加洗头频率";
            oil.serverity = "重度";
        }
    }
    // 设置字段值
    env->SetObjectField(javaOilReturn, suggestionField, env->NewStringUTF(oil.suggestion.c_str()));
    env->SetDoubleField(javaOilReturn, levelField, oil.level);
    env->SetIntField(javaOilReturn, statusCodeField, oil.status_code);
    env->SetObjectField(javaOilReturn, describeField, env->NewStringUTF(oil.describe.c_str()));
    env->SetObjectField(javaOilReturn, severityField, env->NewStringUTF(oil.serverity.c_str()));
    return javaOilReturn;
}
JNIEXPORT jobject JNICALL
Java_com_tencent_yolov8ncnn_Yolov8Ncnn_sensitiveDetect(JNIEnv *env, jobject thiz, jobject bmpIn,
                                                       jobject bmpOut) {
    // TODO: implement sensitiveDetect()
    sensitive_return sensitive;
    // 获取 OilReturn 类
    jclass sensitiveReturnClass = env->FindClass("com/tencent/yolov8ncnn/seneitiveReturn");
    // 创建 OilReturn 对象
    jobject javaOilReturn = env->AllocObject(sensitiveReturnClass);
    __android_log_print(ANDROID_LOG_DEBUG,"ncnn","usbInfer:start");
    AndroidBitmapInfo inBmpInfo;
    void* inPixelsAddress;
    int ret;
    if((ret= AndroidBitmap_getInfo(env,bmpIn,&inBmpInfo))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_getInfo() failed! error=%d", ret);
        return javaOilReturn;
    }
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "original image::width is %d,height is %d,format is %d,flags is %d,stride is %u", inBmpInfo.width,inBmpInfo.height,inBmpInfo.format,inBmpInfo.flags,inBmpInfo.stride);
    if((ret= AndroidBitmap_lockPixels(env,bmpIn,&inPixelsAddress))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_lockPixels() failed! error=%d", ret);
    }
    cv::Mat inMat(inBmpInfo.height,inBmpInfo.width,CV_8UC4,inPixelsAddress);
    cv::cvtColor(inMat, inMat, cv::COLOR_RGBA2BGR);
    int row=inMat.rows;
    int col=inMat.cols;
    int dims=inMat.channels();
    int label=0;
    cv::Mat ycbcr_img;
    cv::cvtColor(inMat,ycbcr_img,cv::COLOR_BGR2HSV);
    cv::Mat mask=inMat.clone();
    int h1min = 0;
    int h1max = 10;
    int h2min = 156;
    int h2max = 180;
    int smin = 43;
    int smax = 255;
    int vmin = 46;
    int vmax = 255;
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col * dims; j += 3) {

            int h = ycbcr_img.at<uchar>(i, j);
            int s = ycbcr_img.at<uchar>(i, j + 1);
            int v = ycbcr_img.at<uchar>(i, j + 2);
            /* if (i == 796 && j == 1221) {
                 cout << "H:" <<h << "S:" << s << "V:" <<v<< endl;
             }*/
            if ((((h < h1max) && (h > h1min))||((h<h2max)&&(h>h2min))) && (s < smax) && (s > smin) && (v < vmax) && (v > vmin)) {
                label += 1;
                mask.at<uchar>(i, j) = 0;
                mask.at<uchar>(i, j + 1) = 0;
                mask.at<uchar>(i, j + 2) = 204;
            }
        }
    }
    double s=1.0*label/(row*col);
    AndroidBitmapInfo outBmpInfo;
    void* outPixelsAddress;
    if ((ret = AndroidBitmap_getInfo(env, bmpOut, &outBmpInfo)) < 0) {
//        LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return javaOilReturn;
    }

    if ((ret = AndroidBitmap_lockPixels(env, bmpOut, &outPixelsAddress)) < 0) {
//        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return javaOilReturn;
    }
    cv::Mat outMat(outBmpInfo.height, outBmpInfo.width, CV_8UC4, outPixelsAddress);
    cv::cvtColor(mask, outMat, cv::COLOR_BGR2RGBA);
    //相当于直接操作了内存地址
    AndroidBitmap_unlockPixels(env,bmpIn);
    AndroidBitmap_unlockPixels(env, bmpOut);
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "return!!!");
    // 获取字段 ID
    jfieldID suggestionField = env->GetFieldID(sensitiveReturnClass, "suggestion", "Ljava/lang/String;");
    jfieldID levelField = env->GetFieldID(sensitiveReturnClass, "level", "D");
    jfieldID statusCodeField = env->GetFieldID(sensitiveReturnClass, "status_code", "I");
    jfieldID describeField = env->GetFieldID(sensitiveReturnClass, "describe", "Ljava/lang/String;");
    jfieldID severityField = env->GetFieldID(sensitiveReturnClass, "severity", "Ljava/lang/String;");
    //处理油脂逻辑
    if (s < 0.25) {
        double base = static_cast<double>(1) / 25;
        sensitive.level = 6 - base * s * 100;
        sensitive.level = round(sensitive.level * 100) / 100;
        sensitive.describe = "受到外界刺激的情况下，头皮个别情况下会出现敏感状态";
        sensitive.suggestion = "舒缓，保养";
        sensitive.serverity = "适中";
    }
    else if (s >= 0.25 && s < 0.5) {
        double base = static_cast <double>(2) / 25;
        sensitive.level = 5 - base * (s * 100 - 25);
        sensitive.level = round(sensitive.level * 100) / 100;
        sensitive.describe = "红血丝呈短线状，头皮颜色不均，干痒，不时伴有轻度红肿状态";
        sensitive.suggestion = "镇定，修复，退红，去痒";
        sensitive.serverity = "轻度";
    }
    else if (0.5 <= s && s < 0.75) {
        double base = static_cast <double>(2) / 25;
        sensitive.level = 3 - base * (s * 100 - 50);
        sensitive.level = round(sensitive.level * 100) / 100;
        sensitive.describe = "头皮表层呈短片红状，皮下组织肉眼可见网状红血丝，严重时伴有红肿痒";
        sensitive.suggestion = "修复皮下有棘层，供给营养，增强头皮免疫";
        sensitive.serverity = "中度";
    }
    else {
        double base = static_cast <double>(1) / 25;
        sensitive.level = 1 - base * (s * 100 - 75);
        sensitive.level = round(sensitive.level * 100) / 100;
        sensitive.describe = "表层可见头皮红片，红块形态，皮野，皮沟，皮丘呈现状态粗糙，头皮基底层受损";
        sensitive.suggestion = "修复皮脂膜，镇定舒缓，修复肌底细胞组织，加强基底代谢，避免毛囊受损";
        sensitive.serverity = "重度";
    }
    // 设置字段值
    env->SetObjectField(javaOilReturn, suggestionField, env->NewStringUTF(sensitive.suggestion.c_str()));
    env->SetDoubleField(javaOilReturn, levelField, sensitive.level);
    env->SetIntField(javaOilReturn, statusCodeField, sensitive.status_code);
    env->SetObjectField(javaOilReturn, describeField, env->NewStringUTF(sensitive.describe.c_str()));
    env->SetObjectField(javaOilReturn, severityField, env->NewStringUTF(sensitive.serverity.c_str()));
    return javaOilReturn;
}
JNIEXPORT jobject JNICALL
Java_com_tencent_yolov8ncnn_Yolov8Ncnn_densityanddiameterDetect(JNIEnv *env, jobject thiz, jobject bmpIn,
                                                     jobject bmpOut,jint flag) {
    // TODO: implement densityDetect()

    // 获取 TotalInfo 类
    jclass TotalInfoClass = env->FindClass("com/tencent/yolov8ncnn/TotalInfo");
    // 创建 OilReturn 对象
    jobject javaTotalInfo = env->AllocObject(TotalInfoClass);
    __android_log_print(ANDROID_LOG_DEBUG,"ncnn","density:start");
    AndroidBitmapInfo inBmpInfo;
    void* inPixelsAddress;
    int ret;
    if((ret= AndroidBitmap_getInfo(env,bmpIn,&inBmpInfo))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_getInfo() failed! error=%d", ret);
        return javaTotalInfo;
    }
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "original image::width is %d,height is %d,format is %d,flags is %d,stride is %u", inBmpInfo.width,inBmpInfo.height,inBmpInfo.format,inBmpInfo.flags,inBmpInfo.stride);
    if((ret= AndroidBitmap_lockPixels(env,bmpIn,&inPixelsAddress))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_lockPixels() failed! error=%d", ret);
    }
    cv::Mat inMat(inBmpInfo.height,inBmpInfo.width,CV_8UC4,inPixelsAddress);
    cv::cvtColor(inMat, inMat, cv::COLOR_RGBA2BGR);
    std::vector<Object> final;
    int unit=0;
    double s=0;
    std::vector<cv::Scalar> color;
    color.push_back(cv::Scalar (0,0,255));
    color.push_back(cv::Scalar (0,255,0));
    color.push_back(cv::Scalar (255,0,0));
    double level;
    std::string suggestion;
    std::string describe;
    std::string score_type;
    std::vector<hairInfo> hairinfo;
    std::vector<unitInfo> unitinfo;
    totalInfo totalinfo;
    if (unit_yolo) {

        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "detect");
        std::vector<Object> objects;
        unit_yolo->detect(inMat, objects);
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "detect=%d", objects.size());
        if(objects.size()>0) {
            __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "label=%d", objects[0].label);
        }
        float ratio=4;
        unit_yolo->info_init(inMat,objects,unitinfo,hairinfo,ratio);
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "diameter=%d",1);
        float eps=200;
        float minPts=1;
        unit_yolo->info_make(hairinfo,unitinfo,totalinfo,eps,minPts);
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "diameter=%d",2);
        float area=0.18;
        totalinfo.hairDensity=totalinfo.hairNum/area;
        totalinfo.unitDensity=totalinfo.unitNum/area;
        if(flag==1){
            for(int index=0;index<unitinfo.size();index++){
                cv::Point tl=unitinfo[index].unitCnt.tl();
                cv::Point br=unitinfo[index].unitCnt.br();
                float area=unitinfo[index].unitCnt.area();
                if(area>0){
                    unit+=1;
                    int hair_number=unitinfo[index].hairlist.size();
                    std::string label="p"+std::to_string(hair_number);
                    int baseline;
                    cv::Size labelSize=cv::getTextSize(label,cv::FONT_HERSHEY_SIMPLEX,0.5,1,&baseline);
                    if(hair_number==1){
                        cv::rectangle(inMat,tl,br,color[0],2);
                        cv::putText(inMat,label,tl,cv::FONT_HERSHEY_SIMPLEX,0.7,color[0],2);
                    }
                    else if(hair_number==2){
                        cv::rectangle(inMat,tl,br,color[1],2);
                        cv::putText(inMat,label,tl,cv::FONT_HERSHEY_SIMPLEX,0.7,color[1],2);
                    }
                    else if(hair_number>2){
                        cv::rectangle(inMat,tl,br,color[2],2);
                        cv::putText(inMat,label,tl,cv::FONT_HERSHEY_SIMPLEX,0.7,color[2],2);
                    }
                }
            }
            double average_density=round(totalinfo.hairDensity*100)/100;
            std::cout<<"当前的平均毛发密度为"<<average_density<<std::endl;
            if (average_density <= 172.19 && average_density > 116.27) {
                double base = static_cast<double>(1) / 55.92;
                level = 5 + base * (average_density - 116.27);
                level = round(level * 100) / 100;
                describe = "当前平均毛发密度为" + std::to_string(average_density).substr(0,5) + "根/平方厘米";
                score_type = "适中";
                suggestion = "营养毛囊";
            }
            else if (average_density > 77.52 && average_density <= 116.27) {
                double base = static_cast<double>(2) / 38.75;
                level = 3 + base * (average_density - 77.52);
                level = round(level * 100) / 100;
                describe = "当前的平均毛发密度为" + std::to_string(average_density).substr(0, 5) + "根/平方厘米";
                score_type = "轻度";
                suggestion = "激活空置毛囊，降低单根占比";
            }
            else if (average_density > 38.77 && average_density <= 77.52) {
                double base = static_cast<double>(2) / 38.75;
                level = 1 + base * (average_density - 0.03);
                level = round(level * 100) / 100;
                describe = "当前的平均毛发密度为" + std::to_string(average_density).substr(0, 5) + "根/平方厘米";
                score_type = "中度";
                suggestion = "激活空置毛囊，增加毛发数量，增加多根占比";
            }
            else if (average_density >= 0 && average_density <= 38.77) {
                double base = static_cast<double>(1) / 38.77;
                level = base * (average_density - 0);
                level = round(level * 100) / 100;
                describe = "当前的平均毛发密度为" + std::to_string(average_density).substr(0, 5) + "根/平方厘米";
                score_type = "重度";
                suggestion = "激活空置毛囊，增加毛发数量，增加单双根健康占比";
            }
            else {
                level = 6.0;
                describe = "当前的平均毛发密度为" + std::to_string(average_density).substr(0, 5) + "根/平方厘米";
                score_type = "适中";
                suggestion = "营养毛囊";
            }
        }else{
            std::vector<std::string>classname={"fine","moderate","wide"};
            unit_yolo->DrawPred(inMat,objects,classname,color,unitinfo);
            double average_diameter=floor(totalinfo.meanDiameter)/1000;
            std::cout<<"当前的平均毛发直径为"<<average_diameter<<std::endl;
            if (average_diameter <= 0.09 && average_diameter > 0.07) {
                double base = static_cast<double>(1) / 0.02;
                level = 5 + base*(average_diameter-0.07);
                level = round(level * 100) / 100;
                describe = "当前平均毛发直径为"+std::to_string(average_diameter).substr(0, 5) +"mm";
                score_type = "适中";
                suggestion= "营养毛囊，日常护理";
            }
            else if (average_diameter > 0.05 && average_diameter <= 0.07) {
                double base = static_cast<double>(2) / 0.02;
                level = 3 + base * (average_diameter-0.05);
                level = round(level * 100) / 100;
                describe = "当前的平均毛发直径为"+std::to_string(average_diameter).substr(0, 5) +"mm";
                score_type = "轻度";
                suggestion = "建立养分通道，供给营养，促进血氧，增粗毛发";
            }
            else if (average_diameter > 0.03 && average_diameter <= 0.05) {
                double base = static_cast<double>(2) / 0.02;
                level = 1 + base * (average_diameter - 0.03);
                level = round(level * 100) / 100;
                describe = "当前的平均毛发直径为"+std::to_string(average_diameter).substr(0, 5) +"mm";
                score_type = "中度";
                suggestion = "营养供给，稳固增粗，激活休止期毛囊";
            }
            else if(average_diameter>=0&&average_diameter<=0.03){
                double base = static_cast<double>(1) / 0.02;
                level =  base * (average_diameter - 0);
                level = round(level * 100) / 100;
                describe = "当前的平均毛发直径为" + std::to_string(average_diameter).substr(0, 5) + "mm";
                score_type = "重度";
                suggestion = "建立营养通道，修复基地组织，激活衰退毛囊，刺激毛母细胞分裂，恢复健康覆盖率";
            }
            else {
                level = 6.0;
                describe = "当前的平均毛发直径为" + std::to_string(average_diameter).substr(0, 5) + "mm";
                score_type = "适中";
                suggestion = "建立营养通道，修复基地组织，激活衰退毛囊，刺激毛母细胞分裂，恢复健康覆盖率";
            }
        }
//        g_yolo->draw(inMat, objects,s);
    } else {
        draw_unsupported(inMat);
    }

    AndroidBitmapInfo outBmpInfo;
    void* outPixelsAddress;

    if ((ret = AndroidBitmap_getInfo(env, bmpOut, &outBmpInfo)) < 0) {
//        LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return javaTotalInfo;
    }

    if ((ret = AndroidBitmap_lockPixels(env, bmpOut, &outPixelsAddress)) < 0) {
//        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return javaTotalInfo;
    }
    cv::Mat outMat(outBmpInfo.height, outBmpInfo.width, CV_8UC4, outPixelsAddress);
    cv::cvtColor(inMat, outMat, cv::COLOR_BGR2RGBA);
    //相当于直接操作了内存地址
    AndroidBitmap_unlockPixels(env,bmpIn);
    AndroidBitmap_unlockPixels(env, bmpOut);
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "return!!!");
    // 获取字段 ID
    jfieldID hairNumField=env->GetFieldID(TotalInfoClass,"hairNum","I");
    jfieldID hairDensityField=env->GetFieldID(TotalInfoClass,"hairDensity","D");
    jfieldID meanDiameterField=env->GetFieldID(TotalInfoClass,"meanDiameter","D");
    jfieldID unitNumField=env->GetFieldID(TotalInfoClass,"unitNum","I");
    jfieldID unitDensityField=env->GetFieldID(TotalInfoClass,"unitDensity","D");
    jfieldID suggestionField = env->GetFieldID(TotalInfoClass, "suggestion", "Ljava/lang/String;");
    jfieldID levelField = env->GetFieldID(TotalInfoClass, "level", "D");
    jfieldID statusCodeField = env->GetFieldID(TotalInfoClass, "status_code", "I");
    jfieldID describeField = env->GetFieldID(TotalInfoClass, "describe", "Ljava/lang/String;");
    jfieldID severityField = env->GetFieldID(TotalInfoClass, "severity", "Ljava/lang/String;");
    // 设置字段值
    env->SetIntField(javaTotalInfo,hairNumField,totalinfo.hairNum);
    env->SetDoubleField(javaTotalInfo,hairDensityField,totalinfo.unitDensity);
    env->SetDoubleField(javaTotalInfo,meanDiameterField,totalinfo.meanDiameter);
    env->SetIntField(javaTotalInfo,unitNumField,totalinfo.unitNum);
    env->SetDoubleField(javaTotalInfo,unitDensityField,totalinfo.unitDensity);
    env->SetObjectField(javaTotalInfo, suggestionField, env->NewStringUTF(suggestion.c_str()));
    env->SetDoubleField(javaTotalInfo, levelField, level);
    env->SetIntField(javaTotalInfo, statusCodeField, 0);
    env->SetObjectField(javaTotalInfo, describeField, env->NewStringUTF(describe.c_str()));
    env->SetObjectField(javaTotalInfo, severityField, env->NewStringUTF(score_type.c_str()));
    return javaTotalInfo;
}
JNIEXPORT jobject JNICALL
Java_com_tencent_yolov8ncnn_Yolov8Ncnn_surfaceOilDetect(JNIEnv *env, jobject thiz, jobject bmpIn,
                                                        jobject bmpOut) {
    // TODO: implement surfaceOilDetect()
    oil_return oil;
    // 获取 OilReturn 类
    jclass sensitiveReturnClass = env->FindClass("com/tencent/yolov8ncnn/OilReturn");
    // 创建 OilReturn 对象
    jobject javaOilReturn = env->AllocObject(sensitiveReturnClass);
    __android_log_print(ANDROID_LOG_DEBUG,"ncnn","usbInfer:start");
    AndroidBitmapInfo inBmpInfo;
    void* inPixelsAddress;
    int ret;
    if((ret= AndroidBitmap_getInfo(env,bmpIn,&inBmpInfo))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_getInfo() failed! error=%d", ret);
        return javaOilReturn;
    }
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "original image::width is %d,height is %d,format is %d,flags is %d,stride is %u", inBmpInfo.width,inBmpInfo.height,inBmpInfo.format,inBmpInfo.flags,inBmpInfo.stride);
    if((ret= AndroidBitmap_lockPixels(env,bmpIn,&inPixelsAddress))<0){
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "AndroidBitmap_lockPixels() failed! error=%d", ret);
    }
    cv::Mat inMat(inBmpInfo.height,inBmpInfo.width,CV_8UC4,inPixelsAddress);
    cv::cvtColor(inMat, inMat, cv::COLOR_RGBA2BGR);
    int row=inMat.rows;
    int col=inMat.cols;
    int dims=inMat.channels();
    int label=0;
    std::string method="HSV";
    int threshold=180;
    cv::Mat _img;
    cv::Mat mask=inMat.clone();
    if(method=="HSV"){
        cv::cvtColor(inMat,_img,cv::COLOR_BGR2HSV);
        for(int i=0;i<row;i++){
            for (int j = 2; j < col*dims; j+=3) {
                int v=_img.at<uchar>(i,j);
                if (v > threshold){
                    label+= 1;
                    mask.at<uchar>(i,j-2)=0;
                    mask.at<uchar>(i,j-1)=255;
                    mask.at<uchar>(i,j)=0;}

            }}}
    else if(method=="HLS"){
        cv::cvtColor(inMat,_img,cv::COLOR_BGR2HLS);
        for(int i=0;i<row;i++){
            for (int j = 1; j < col*dims; j+=3) {
                int l=_img.at<uchar>(i,j);
                if (l > threshold){
                    label+= 1;
                    mask.at<uchar>(i,j-1)=0;
                    mask.at<uchar>(i,j)=255;
                    mask.at<uchar>(i,j+1)=0;}
            }}}
    else
        std::cout<<"油脂检测类型输入错误！填入‘HSV’或者‘HLS’。"<<std::endl;
    double s=1.0*label/(row*col);
    AndroidBitmapInfo outBmpInfo;
    void* outPixelsAddress;
    if ((ret = AndroidBitmap_getInfo(env, bmpOut, &outBmpInfo)) < 0) {
//        LOGD("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return javaOilReturn;
    }

    if ((ret = AndroidBitmap_lockPixels(env, bmpOut, &outPixelsAddress)) < 0) {
//        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return javaOilReturn;
    }
    cv::Mat outMat(outBmpInfo.height, outBmpInfo.width, CV_8UC4, outPixelsAddress);
    cv::cvtColor(mask, outMat, cv::COLOR_BGR2RGBA);
    //相当于直接操作了内存地址
    AndroidBitmap_unlockPixels(env,bmpIn);
    AndroidBitmap_unlockPixels(env, bmpOut);
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "return!!!");
    // 获取字段 ID
    jfieldID suggestionField = env->GetFieldID(sensitiveReturnClass, "suggestion", "Ljava/lang/String;");
    jfieldID levelField = env->GetFieldID(sensitiveReturnClass, "level", "D");
    jfieldID statusCodeField = env->GetFieldID(sensitiveReturnClass, "status_code", "I");
    jfieldID describeField = env->GetFieldID(sensitiveReturnClass, "describe", "Ljava/lang/String;");
    jfieldID severityField = env->GetFieldID(sensitiveReturnClass, "severity", "Ljava/lang/String;");
    //处理油脂逻辑
    if (s < 0.25) {
        double base = static_cast<double>(1) / 25;
        oil.level = 6 - base * s * 100;
        oil.level = round(oil.level * 100) / 100;
        oil.describe = "水油不均，油脂形态初步形成";
        oil.suggestion = "基础清洁";
        oil.serverity = "适中";
    }
    else if (s >= 0.25 && s < 0.5) {
        double base = static_cast <double>(2) / 25;
        oil.level = 5 - base * (s * 100 - 25);
        oil.level = round(oil.level * 100) / 100;
        oil.describe = "油脂呈半流动液态，附着在毛孔周边，油脂分泌轻度旺盛，影响表皮养分吸收以及摄入营养";
        oil.suggestion = "清洁油脂，调整头皮酸碱度";
        oil.serverity = "轻度";
    }
    else if (0.5 <= s && s < 0.75) {
        double base = static_cast <double>(2) / 25;
        oil.level = 3 - base * (s * 100 - 50);
        oil.level = round(oil.level * 100) / 100;
        oil.describe = "油脂呈液态，发干根部明显油腻，油脂分泌旺盛，形成半液态状对毛囊口呈围堵状态，养分摄入不均，拉长毛发代谢周期，影响毛发健康生长";
        oil.suggestion = "清洁纠正，调节DHT，供给营养";
        oil.serverity = "中度";
    }
    else {
        double base = static_cast <double>(1) / 25;
        oil.level = 1 - base * (s * 100 - 75);
        oil.level = round(oil.level * 100) / 100;
        oil.describe = "油脂呈凝固状，蜡状物体包裹在毛囊口以及发干周边，蜡状物是分泌过多的油脂后，油脂吸附空气当中的灰尘等和头屑角质混合而成的类似蜡状物的东西，不易清除，长期附着堵塞毛囊口，循环代谢差，血氧供给不足，导致毛发逐步退化。";
        oil.suggestion = "清洁纠正，配合仪器以及敏感修复配方的同时，分解去除蜡状角质，补给营养";
        oil.serverity = "重度";
    }
    // 设置字段值
    env->SetObjectField(javaOilReturn, suggestionField, env->NewStringUTF(oil.suggestion.c_str()));
    env->SetDoubleField(javaOilReturn, levelField, oil.level);
    env->SetIntField(javaOilReturn, statusCodeField, oil.status_code);
    env->SetObjectField(javaOilReturn, describeField, env->NewStringUTF(oil.describe.c_str()));
    env->SetObjectField(javaOilReturn, severityField, env->NewStringUTF(oil.serverity.c_str()));
    return javaOilReturn;
}

}




